/*
 * SoapUI, Copyright (C) 2004-2017 SmartBear Software
 *
 * Licensed under the EUPL, Version 1.1 or - as soon as they will be approved by the European Commission - subsequent 
 * versions of the EUPL (the "Licence"); 
 * You may not use this work except in compliance with the Licence. 
 * You may obtain a copy of the Licence at: 
 * 
 * http://ec.europa.eu/idabc/eupl 
 * 
 * Unless required by applicable law or agreed to in writing, software distributed under the Licence is 
 * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 
 * express or implied. See the Licence for the specific language governing permissions and limitations 
 * under the Licence. 
 */

package com.eviware.soapui.impl.wsdl.teststeps;

import com.eviware.soapui.config.PropertiesStepConfig;
import com.eviware.soapui.config.TestStepConfig;
import com.eviware.soapui.impl.wsdl.MutableTestPropertyHolder;
import com.eviware.soapui.impl.wsdl.support.ExternalDependency;
import com.eviware.soapui.impl.wsdl.support.PathPropertyExternalDependency;
import com.eviware.soapui.impl.wsdl.support.XmlBeansPropertiesTestPropertyHolder;
import com.eviware.soapui.impl.wsdl.testcase.WsdlTestCase;
import com.eviware.soapui.model.propertyexpansion.PropertyExpander;
import com.eviware.soapui.model.propertyexpansion.PropertyExpansionUtils;
import com.eviware.soapui.model.testsuite.TestCaseRunContext;
import com.eviware.soapui.model.testsuite.TestCaseRunner;
import com.eviware.soapui.model.testsuite.TestProperty;
import com.eviware.soapui.model.testsuite.TestPropertyListener;
import com.eviware.soapui.model.testsuite.TestStepResult;
import com.eviware.soapui.model.testsuite.TestStepResult.TestStepStatus;
import com.eviware.soapui.support.StringUtils;
import com.eviware.soapui.support.UISupport;
import com.eviware.soapui.support.resolver.ResolveContext;
import com.eviware.soapui.support.types.StringList;

import javax.swing.ImageIcon;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;

/**
 * TestStep that holds an arbitrary number of custom properties
 *
 * @author ole.matzura
 */

public class WsdlPropertiesTestStep extends WsdlTestStep implements MutableTestPropertyHolder {
    private PropertiesStepConfig propertiesStepConfig;
    private ImageIcon okIcon;
    private ImageIcon failedIcon;
    private XmlBeansPropertiesTestPropertyHolder propertyHolderSupport;
    private BeanPathPropertySupport sourceProperty;
    private BeanPathPropertySupport targetProperty;

    public static final String SOURCE_PROPERTY = WsdlPropertiesTestStep.class.getName() + "@source";
    public static final String TARGET_PROPERTY = WsdlPropertiesTestStep.class.getName() + "@target";

    public WsdlPropertiesTestStep(WsdlTestCase testCase, TestStepConfig config, boolean forLoadTest) {
        super(testCase, config, true, forLoadTest);

        if (!forLoadTest) {
            okIcon = UISupport.createImageIcon("/properties_step.png");
            failedIcon = UISupport.createImageIcon("/failed_properties_step.png");

            setIcon(okIcon);
        }

        if (config.getConfig() == null) {
            propertiesStepConfig = (PropertiesStepConfig) config.addNewConfig().changeType(PropertiesStepConfig.type);
            propertiesStepConfig.addNewProperties();
            propertiesStepConfig.setCreateMissingOnLoad(true);
        } else {
            propertiesStepConfig = (PropertiesStepConfig) config.getConfig().changeType(PropertiesStepConfig.type);
            if (!propertiesStepConfig.isSetProperties()) {
                propertiesStepConfig.addNewProperties();
            }

            if (!propertiesStepConfig.isSetSaveFirst()) {
                propertiesStepConfig.setSaveFirst(true);
            }
        }

        propertyHolderSupport = new XmlBeansPropertiesTestPropertyHolder(this, propertiesStepConfig.getProperties());
        sourceProperty = new BeanPathPropertySupport(this, propertiesStepConfig, "source");
        targetProperty = new BeanPathPropertySupport(this, propertiesStepConfig, "target");
    }

    public TestStepResult run(TestCaseRunner testRunner, TestCaseRunContext testRunContext) {
        WsdlTestStepResult result = new WsdlTestStepResult(this);

        if (okIcon != null) {
            setIcon(okIcon);
        }

        result.setStatus(TestStepStatus.OK);
        result.startTimer();

        if (isSaveFirst()) {
            saveDuringRun(result, testRunContext);
        }

        String source = sourceProperty.expand(testRunContext);
        if (StringUtils.hasContent(source)) {
            try {
                int cnt = loadProperties(source, isCreateMissingOnLoad());

                result.setStatus(TestStepStatus.OK);
                result.addMessage("Loaded " + cnt + " properties from [" + source + "]");
            } catch (IOException e) {
                result.stopTimer();
                result.addMessage("Failed to load properties from [" + source + "]");
                result.setStatus(TestStepStatus.FAILED);
                result.setError(e);

                if (failedIcon != null) {
                    setIcon(failedIcon);
                }
            }
        }

        if (!isSaveFirst()) {
            saveDuringRun(result, testRunContext);
        }

        result.stopTimer();

        return result;
    }

    private boolean saveDuringRun(WsdlTestStepResult result, TestCaseRunContext context) {
        String target = targetProperty.expand(context);
        if (StringUtils.hasContent(target)) {
            try {
                int cnt = saveProperties(target);

                result.setStatus(TestStepStatus.OK);
                result.addMessage("Saved " + cnt + " properties to [" + target + "]");
            } catch (IOException e) {
                result.stopTimer();
                result.addMessage("Failed to save properties to [" + target + "]");
                result.setStatus(TestStepStatus.FAILED);
                result.setError(e);

                if (failedIcon != null) {
                    setIcon(failedIcon);
                }

                return false;
            }
        }

        return true;
    }

    private int saveProperties(String target) throws IOException {
        return propertyHolderSupport.saveTo(System.getProperty(target, target));
    }

    private int loadProperties(String source, boolean createMissing) throws IOException {
        // override methods so propertynames are returned in readorder
        java.util.Properties props = new java.util.Properties() {
            public StringList names = new StringList();

            @Override
            public synchronized Object put(Object key, Object value) {
                names.add(key.toString());
                return super.put(key, value);
            }

            @Override
            public Enumeration<?> propertyNames() {
                return Collections.enumeration(names);
            }
        };

        InputStream in = getPropertiesInputStream(source);
        props.load(in);
        in.close();

        int cnt = 0;
        Enumeration<?> names = props.propertyNames();
        while (names.hasMoreElements()) {
            String name = names.nextElement().toString();
            TestProperty property = getProperty(name);
            if (property != null) {
                property.setValue(props.get(name).toString());
                cnt++;
            } else if (createMissing) {
                addProperty(name).setValue(props.get(name).toString());
                cnt++;
            }
        }

        return cnt;
    }

    private InputStream getPropertiesInputStream(String source) throws IOException {
        String fileProperty = System.getProperty(source);
        if (fileProperty != null) {
            source = fileProperty;
        }

        URL url;

        try {
            url = new URL(source);
        } catch (MalformedURLException e) {
            url = new URL("file:" + source);
        }

        return url.openStream();
    }

    public TestProperty getTestStepPropertyAt(int index) {
        return propertyHolderSupport.getPropertyAt(index);
    }

    public int getStepPropertyCount() {
        return propertyHolderSupport.getPropertyCount();
    }

    public String getSource() {
        return sourceProperty.get();
    }

    public void setSource(String source) {
        sourceProperty.set(source, true);
    }

    public String getTarget() {
        return targetProperty.get();
    }

    public String getLabel() {
        String str = super.getName() + " (" + getPropertyCount() + ")";

        if (isDisabled()) {
            str += " (disabled)";
        }

        return str;
    }

    public void setTarget(String target) {
        targetProperty.set(target, true);
    }

    public void resetConfigOnMove(TestStepConfig config) {
        super.resetConfigOnMove(config);

        propertiesStepConfig = (PropertiesStepConfig) config.getConfig().changeType(PropertiesStepConfig.type);
        propertyHolderSupport.resetPropertiesConfig(propertiesStepConfig.getProperties());
        sourceProperty.setConfig(propertiesStepConfig);
        targetProperty.setConfig(propertiesStepConfig);
    }

    public int loadProperties(boolean createMissing) throws IOException {
        return loadProperties(sourceProperty.expand(), createMissing);
    }

    public int saveProperties() throws IOException {
        String target = PropertyExpander.expandProperties(this, targetProperty.expand());
        return saveProperties(target);
    }

    public boolean isCreateMissingOnLoad() {
        return propertiesStepConfig.getCreateMissingOnLoad();
    }

    public void setCreateMissingOnLoad(boolean b) {
        propertiesStepConfig.setCreateMissingOnLoad(b);
    }

    public boolean isSaveFirst() {
        return propertiesStepConfig.getSaveFirst();
    }

    public void setSaveFirst(boolean b) {
        propertiesStepConfig.setSaveFirst(b);
    }

    public boolean isDiscardValuesOnSave() {
        return propertiesStepConfig.getDiscardValuesOnSave();
    }

    public void setDiscardValuesOnSave(boolean b) {
        propertiesStepConfig.setDiscardValuesOnSave(b);
    }

    public void setPropertyValue(String name, String value) {
        if (isCreateMissingOnLoad() && getProperty(name) == null) {
            addProperty(name);
        }

        propertyHolderSupport.setPropertyValue(name, value);
    }

    @Override
    public void beforeSave() {
        super.beforeSave();

        if (isDiscardValuesOnSave()) {
            clearPropertyValues();
        }
    }

    public void clearPropertyValues() {
        for (TestProperty property : propertyHolderSupport.getProperties().values()) {
            property.setValue(null);
        }
    }

    public boolean renameProperty(String name, String newName) {
        return PropertyExpansionUtils.renameProperty(propertyHolderSupport.getProperty(name), newName, getTestCase()) != null;
    }

    public TestProperty addProperty(String name) {
        String oldLabel = getLabel();

        TestProperty property = propertyHolderSupport.addProperty(name);
        notifyPropertyChanged(WsdlTestStep.LABEL_PROPERTY, oldLabel, getLabel());

        return property;
    }

    public void addTestPropertyListener(TestPropertyListener listener) {
        propertyHolderSupport.addTestPropertyListener(listener);
    }

    public Map<String, TestProperty> getProperties() {
        return propertyHolderSupport.getProperties();
    }

    public TestProperty getProperty(String name) {
        return propertyHolderSupport.getProperty(name);
    }

    public TestProperty getPropertyAt(int index) {
        return propertyHolderSupport.getPropertyAt(index);
    }

    public List<TestProperty> getPropertyList() {
        return propertyHolderSupport.getPropertyList();
    }

    public int getPropertyCount() {
        return propertyHolderSupport.getPropertyCount();
    }

    public String[] getPropertyNames() {
        return propertyHolderSupport.getPropertyNames();
    }

    public String getPropertyValue(String name) {
        return propertyHolderSupport.getPropertyValue(name);
    }

    public TestProperty removeProperty(String propertyName) {
        String oldLabel = getLabel();

        TestProperty result = propertyHolderSupport.removeProperty(propertyName);
        notifyPropertyChanged(WsdlTestStep.LABEL_PROPERTY, oldLabel, getLabel());
        return result;
    }

    public void removeTestPropertyListener(TestPropertyListener listener) {
        propertyHolderSupport.removeTestPropertyListener(listener);
    }

    public boolean hasProperty(String name) {
        return propertyHolderSupport.hasProperty(name);
    }

    public void addExternalDependency(List<ExternalDependency> dependencies) {
        super.addExternalDependencies(dependencies);
        dependencies.add(new PathPropertyExternalDependency(targetProperty));
        dependencies.add(new PathPropertyExternalDependency(sourceProperty));
    }

    @Override
    public void resolve(ResolveContext<?> context) {
        super.resolve(context);

        targetProperty.resolveFile(context, "Missing target property file", "properties",
                "Properties Files (*.properties)", true);
        sourceProperty.resolveFile(context, "Missing source property file", "properties",
                "Properties Files (*.properties)", true);
    }

    public void moveProperty(String propertyName, int targetIndex) {
        propertyHolderSupport.moveProperty(propertyName, targetIndex);
    }

    public String getSource(boolean expand) {
        return expand ? sourceProperty.expand() : getSource();
    }

    public String getTarget(boolean expand) {
        return expand ? targetProperty.expand() : getTarget();
    }
}
